﻿using System;
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp;
using System.Linq;
using System.Linq.Expressions;
using SharpAnalyzer.Analyzers;
using SharpAnalyzer.Entities;
using SharpAnalyzer.Utilities;
using Microsoft.CodeAnalysis.FindSymbols;

namespace SharpAnalyzer.Calculators
{
    internal class MemberMetricsCalculator
    {
        private SemanticModel semanticModel;
        private Solution solution;

        public MemberMetricsCalculator(Solution solution, SemanticModel semanticModel)
        {
            Verify.NotNull(() => semanticModel);
            Verify.NotNull(() => solution);

            this.semanticModel = semanticModel;
            this.solution = solution;
        }

        public void Calculate(ref Member memberMetric)
        {
            SyntaxNode memberNode = memberMetric.SyntaxNode;

            HalsteadMetrics halsteadMetrics = new HalsteadAnalyzer().Calculate(memberMetric);
            if (halsteadMetrics == null)
                return;

            IEnumerable<string> coupledClasses = new MemberClassCouplingAnalyzer(semanticModel).Calculate(memberMetric);
            int cyclomaticComplexity = new CyclomaticComplexityAnalyzer().Calculate(memberMetric);
            int linesOfCode = new StatementsAnalyzer().Calculate(memberMetric);
            int linesOfComment = CalculateLinesOfComment(memberMetric);
            int numParameters = new MethodParameterAnalyzer().Calculate(memberNode);
            int numLocalVariables = new MethodLocalVariablesAnalyzer().Calculate(memberNode);
            int numOverloads = MethodOverloadsAnalyzer.Calculate(semanticModel, memberNode);
            double maintainabilityIndex = CalculateMaintainabilityIndex((double)cyclomaticComplexity, (double)linesOfCode, halsteadMetrics);
            int afferentCoupling = CalculateAfferentCoupling(memberNode);

            memberMetric.Halstead = halsteadMetrics;
            memberMetric.Metrics = new List<Metric>()
            {
                new Metric()
                {
                    Name = MetricNames.MaintainabilityIndex,
                    Value = maintainabilityIndex
                },
                new Metric()
                {
                    Name = MetricNames.CyclomaticComplexity,
                    Value = cyclomaticComplexity
                },
                new ClassCouplingMetricResult()
                {
                    Name = MetricNames.ClassCoupling,
                    Value = Enumerable.Count<string>(coupledClasses),
                    Types = coupledClasses
                },
                new Metric()
                {
                    Name = MetricNames.LinesOfCode,
                    Value = linesOfCode
                },
                new Metric()
                {
                    Name = MetricNames.LinesOfComment,
                    Value = linesOfComment
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfParameters,
                    Value = numParameters
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfLocalVariables,
                    Value = numLocalVariables
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfOverloads,
                    Value = numOverloads
                },
                new Metric()
                {
                    Name = MetricNames.AfferentCoupling,
                    Value = afferentCoupling
                },
            };
        }

        private int CalculateAfferentCoupling(SyntaxNode node)
        {
            try
            {
                if (solution == null)
                    return 0;

                var symbol = semanticModel.GetDeclaredSymbol(node);
                if (symbol == null)
                    return 0;
                var symbolFinder = SymbolFinder.FindReferencesAsync(symbol, solution).Result;
                return symbolFinder.SelectMany(x => x.Locations).Count();
            }
            catch
            {
                // Some constructors are not present in syntax tree because they have been created for metrics calculation.
                return 0;
            }
        }

        private static int CalculateLinesOfComment(Member memberNode)
        {
            int retval = 0;
            var allTrivia = from syntaxTrivia in memberNode.SyntaxNode.DescendantTrivia() // (Func<SyntaxNode, bool>)null, false)
                            where syntaxTrivia.Kind() == SyntaxKind.SingleLineCommentTrivia || syntaxTrivia.Kind() == SyntaxKind.MultiLineCommentTrivia
                            select syntaxTrivia;
            foreach (var syntaxTrivia in allTrivia)
            {
                FileLinePositionSpan lineSpan = syntaxTrivia.GetLocation().GetLineSpan();
                int num2 = checked(lineSpan.EndLinePosition.Line - lineSpan.StartLinePosition.Line);
                checked { retval += num2 + 1; }
            }
            return retval;
        }

        private static double CalculateMaintainabilityIndex(double cyclomaticComplexity, double linesOfCode, HalsteadMetrics halsteadMetrics)
        {
            if ((linesOfCode == 0.0) ||
                // added from ArchiMetrics!
                (halsteadMetrics.NumOperands == 0 || halsteadMetrics.NumOperators == 0))
                return 100.0;

            double? volume = halsteadMetrics.GetVolume();
            double num = 1.0;
            if (volume.HasValue)
                num = Math.Log(volume.Value);

            // https://blogs.msdn.microsoft.com/zainnab/2011/05/26/code-metrics-maintainability-index/
            // http://blogs.msdn.com/b/codeanalysis/archive/2007/11/20/maintainability-index-range-and-meaning.aspx
            return Math.Round(Math.Max(0.0, (171.0 - 5.2 * num - 0.23 * cyclomaticComplexity - 16.2 * Math.Log(linesOfCode)) * 100.0 / 171.0));
        }
    }
}
